/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <gtest/gtest.h>
#include <securec.h>
#include <stdio.h>
#include <stdlib.h>

#include "accesstoken_kit.h"
#include "cert_manager_api.h"
#include "cm_type.h"
#include "nativetoken_kit.h"
#include "permission_grant_info.h"
#include "token_setproc.h"
#include "tokenid_kit.h"

using namespace std;
using namespace testing::ext;
using namespace OHOS::Security::AccessToken;

static constexpr uint32_t DEFAULT_SIGNATURE_LEN = 1024;

static const uint8_t CA_CERT_URI[] = "oh:t=c;o=5gBasyGLoQUjRnWMt-SyBgPZeIkJJWDI5Ta5ee6VNVIA;u=100;a=20010009";
static const uint8_t USER_CERT_URI[] = "oh:t=sk;o=rRvOqSR_TuM9ez8UwrjzjobvT_jjBEVzUGHG4tCvTKkA;u=100;a=20010009";

PermissionStateFull g_infoAccessUserCaState = { .permissionName = "ohos.permission.ACCESS_USER_TRUSTED_CERT",
    .isGeneral = true,
    .resDeviceID = { "local3" },
    .grantStatus = { PermissionState::PERMISSION_GRANTED },
    .grantFlags = { PermissionFlag::PERMISSION_SYSTEM_FIXED } };

PermissionStateFull g_infoManagerCertState = { .permissionName = "ohos.permission.ACCESS_CERT_MANAGER",
    .isGeneral = true,
    .resDeviceID = { "local3" },
    .grantStatus = { PermissionState::PERMISSION_GRANTED },
    .grantFlags = { PermissionFlag::PERMISSION_SYSTEM_FIXED } };

PermissionStateFull g_infoAccessSysCertState = { .permissionName = "ohos.permission.ACCESS_SYSTEM_APP_CERT",
    .isGeneral = true,
    .resDeviceID = { "local3" },
    .grantStatus = { PermissionState::PERMISSION_GRANTED },
    .grantFlags = { PermissionFlag::PERMISSION_SYSTEM_FIXED } };

HapInfoParams g_tddHapInfoParams = {
    .userID = 100,
    .bundleName = "test.cert",
    .instIndex = 10,
    .appIDDesc = "test",
    .isSystemApp = true,
    .apiVersion = 11 // api version is 11
};

HapPolicyParams g_tddPolicyParams = { .apl = APL_NORMAL,
    .domain = "test.domain",
    .permStateList = { g_infoManagerCertState, g_infoAccessUserCaState, g_infoAccessSysCertState } };

int32_t InitCertInfo(struct CertInfo* certInfo);
int32_t InitCertList(struct CertList** cList);
void FreeCertList(struct CertList* certList);
void FreeCMBlobData(struct CmBlob* blob);
void FreeCertInfo(struct CertInfo* cInfo);
void LogCertInfo(struct CertInfo* certInfo);
void LogCretList(struct CertList* certList);
void LogCredential(struct Credential* certInfo);
void LogCredentialList(struct CredentialList* certificateList);

class CertManagerApiTest : public testing::Test {
public:
    static void SetUpTestCase();
    static void TearDownTestCase();
    void SetUp();
    void TearDown();
};

void CertManagerApiTest::SetUpTestCase()
{
    // clean up test cases
    AccessTokenID tokenId = AccessTokenKit::GetHapTokenID(
        g_tddHapInfoParams.userID, g_tddHapInfoParams.bundleName, g_tddHapInfoParams.instIndex);
    AccessTokenKit::DeleteToken(tokenId);

    AccessTokenIDEx tokenIdEx = AccessTokenKit::AllocHapToken(g_tddHapInfoParams, g_tddPolicyParams);
    SetSelfTokenID(tokenIdEx.tokenIDEx);
}

void CertManagerApiTest::TearDownTestCase()
{
    AccessTokenID tokenId = AccessTokenKit::GetHapTokenID(
        g_tddHapInfoParams.userID, g_tddHapInfoParams.bundleName, g_tddHapInfoParams.instIndex);
    AccessTokenKit::DeleteToken(tokenId);
}

void CertManagerApiTest::SetUp() {}

void CertManagerApiTest::TearDown() {}

HWTEST_F(CertManagerApiTest, CM_USER_TRUSTED_STORE_TEST, TestSize.Level0)
{
    struct CertList* lstCert;
    InitCertList(&lstCert);
    int32_t ret = CmGetUserCertList(CM_USER_TRUSTED_STORE, lstCert);
    if (ret == CM_SUCCESS)
        LogCretList(lstCert);
    FreeCertList(lstCert);
    EXPECT_EQ(ret, CM_SUCCESS) << "CmGetUserCertList failed,retcode:" << ret;
}

HWTEST_F(CertManagerApiTest, CM_SYSTEM_TRUSTED_STORE_TEST, TestSize.Level0)
{
    struct CertList* lstCert;
    InitCertList(&lstCert);
    int32_t ret = CmGetCertList(CM_SYSTEM_TRUSTED_STORE, lstCert);
    if (ret == CM_SUCCESS)
        LogCretList(lstCert);
    FreeCertList(lstCert);
    EXPECT_EQ(ret, CM_SUCCESS) << "CmGetCertList failed,retcode:" << ret;
}

HWTEST_F(CertManagerApiTest, CM_SYS_CREDENTIAL_STORE_TEST, TestSize.Level0)
{
    struct CredentialList certificateList = { 0, nullptr };
    uint32_t buffSize = MAX_COUNT_CERTIFICATE * sizeof(struct CredentialAbstract);
    certificateList.credentialAbstract = static_cast<struct CredentialAbstract*>(malloc(buffSize));
    ASSERT_TRUE(certificateList.credentialAbstract != nullptr);
    certificateList.credentialCount = MAX_COUNT_CERTIFICATE;
    (void)memset_s(certificateList.credentialAbstract, buffSize, 0, buffSize);

    int32_t ret = CmGetAppCertList(CM_SYS_CREDENTIAL_STORE, &certificateList);
    if (ret == CM_SUCCESS)
        LogCredentialList(&certificateList);
    if (certificateList.credentialAbstract != nullptr) {
        free(certificateList.credentialAbstract);
    }
    EXPECT_EQ(ret, CM_SUCCESS) << "CmGetAppCertList test failed, retcode:" << ret;
}

HWTEST_F(CertManagerApiTest, CmGetCACertTest, TestSize.Level0)
{
    struct CmBlob uri = { sizeof(CA_CERT_URI), (uint8_t*)CA_CERT_URI };

    struct CertInfo firstCertInfo;
    unsigned int len = sizeof(struct CertInfo);
    (void)memset_s(&firstCertInfo, len, 0, len);
    int32_t ret = InitCertInfo(&firstCertInfo);
    ret = CmGetUserCertInfo(&uri, CM_USER_TRUSTED_STORE, &firstCertInfo); // CM_SYSTEM_TRUSTED_STORE CM_USER_TRUSTED_STORE
    if (ret == CM_SUCCESS)
        LogCertInfo(&firstCertInfo);
    FreeCMBlobData(&(firstCertInfo.certInfo));
    EXPECT_EQ(ret, CM_SUCCESS) << "CmGetUserCertInfo failed, retcode:" << ret;
}

HWTEST_F(CertManagerApiTest, CmGetAppCertTest, TestSize.Level0)
{
    struct CmBlob uri = { sizeof(USER_CERT_URI), (uint8_t*)USER_CERT_URI };

    struct Credential firstcertificate;
    (void)memset_s(&firstcertificate, sizeof(Credential), 0, sizeof(Credential));
    firstcertificate.credData.data = static_cast<uint8_t*>(malloc(MAX_LEN_CERTIFICATE_CHAIN));
    ASSERT_TRUE(firstcertificate.credData.data != nullptr);
    firstcertificate.credData.size = MAX_LEN_CERTIFICATE_CHAIN;

    int32_t ret = CmGetAppCert(&uri, CM_SYS_CREDENTIAL_STORE, &firstcertificate);
    if (ret == CM_SUCCESS)
        LogCredential(&firstcertificate);
    FreeCMBlobData(&(firstcertificate.credData));

    EXPECT_EQ(ret, CM_SUCCESS) << "CmGetAppCert failed, retcode:" << ret;
}

HWTEST_F(CertManagerApiTest, AppCertSignTest, TestSize.Level0)
{
    struct CmBlob keyUri = { sizeof(USER_CERT_URI), (uint8_t*)USER_CERT_URI };

    struct CmSignatureSpec spec = { CM_KEY_PURPOSE_SIGN, CM_PADDING_PSS, CM_DIGEST_SHA256 };

    const uint8_t messageData[] = "This_is_test_message_for_test_sign_and_verify";
    struct CmBlob message = { 0, nullptr };
    // clang-format off
    uint8_t srcData[] = {
        0xc2, 0xa7, 0xc5, 0x33, 0x79, 0xb0, 0xcd, 0x86, 0x74, 0x09, 0x98, 0x16, 0xd5, 0x85, 0x1b, 0xd6,
        0x87, 0xe3, 0xe0, 0x53, 0x7d, 0xe0, 0xff, 0x1d, 0xdb, 0x27, 0x98, 0xe8, 0x87, 0xe5, 0xb7, 0x03,
    };
    // clang-format on
    if (spec.digest != CM_DIGEST_NONE) {
        message.size = sizeof(messageData);
        message.data = const_cast<uint8_t*>(messageData);
    } else {
        message.size = sizeof(srcData);
        message.data = srcData;
    }

    uint8_t signData[DEFAULT_SIGNATURE_LEN] = { 0 };
    struct CmBlob signature = { DEFAULT_SIGNATURE_LEN, signData };

    uint64_t handleValue = 0;
    struct CmBlob handle = { sizeof(uint64_t), (uint8_t*)&handleValue };

    int32_t ret = CmInit(&keyUri, &spec, &handle);
    EXPECT_EQ(ret, CM_SUCCESS) << "TestSign CmInit test failed";

    ret = CmUpdate(&handle, &message);
    EXPECT_EQ(ret, CM_SUCCESS) << "TestSign CmUpdate test failed";

    struct CmBlob inDataFinish = { 0, nullptr };
    ret = CmFinish(&handle, &inDataFinish, &signature);
    EXPECT_EQ(ret, CM_SUCCESS) << "TestSign CmFinish test failed";

    ret = CmAbort(&handle);
    EXPECT_EQ(ret, CM_SUCCESS) << "TestSign CmAbort test failed";
}

HWTEST_F(CertManagerApiTest, AppCertVerifyTest, TestSize.Level0)
{
    struct CmBlob keyUri = { sizeof(USER_CERT_URI), (uint8_t*)USER_CERT_URI };

    struct CmSignatureSpec spec = { CM_KEY_PURPOSE_SIGN, CM_PADDING_PSS, CM_DIGEST_SHA256 };

    const uint8_t messageData[] = "This_is_test_message_for_test_sign_and_verify";
    struct CmBlob message = { 0, nullptr };
    // clang-format off
    uint8_t srcData[] = {
        0xc2, 0xa7, 0xc5, 0x33, 0x79, 0xb0, 0xcd, 0x86, 0x74, 0x09, 0x98, 0x16, 0xd5, 0x85, 0x1b, 0xd6,
        0x87, 0xe3, 0xe0, 0x53, 0x7d, 0xe0, 0xff, 0x1d, 0xdb, 0x27, 0x98, 0xe8, 0x87, 0xe5, 0xb7, 0x03,
    };
    // clang-format on
    if (spec.digest != CM_DIGEST_NONE) {
        message.size = sizeof(messageData);
        message.data = const_cast<uint8_t*>(messageData);
    } else {
        message.size = sizeof(srcData);
        message.data = srcData;
    }

    uint8_t signData[DEFAULT_SIGNATURE_LEN] = { 0 };
    struct CmBlob signature = { DEFAULT_SIGNATURE_LEN, signData };

    uint64_t handleValue = 0;
    struct CmBlob handle = { sizeof(uint64_t), (uint8_t*)&handleValue };

    int32_t ret = CmInit(&keyUri, &spec, &handle);
    EXPECT_EQ(ret, CM_SUCCESS) << "TestSign CmInit test failed";

    ret = CmUpdate(&handle, &message);
    EXPECT_EQ(ret, CM_SUCCESS) << "TestSign CmUpdate test failed";

    struct CmBlob inDataFinish = { 0, nullptr };
    ret = CmFinish(&handle, &inDataFinish, &signature);
    EXPECT_EQ(ret, CM_SUCCESS) << "TestSign CmFinish test failed";

    ret = CmAbort(&handle);
    EXPECT_EQ(ret, CM_SUCCESS) << "TestSign CmAbort test failed";

    spec.purpose = CM_KEY_PURPOSE_VERIFY;

    ret = CmInit(&keyUri, &spec, &handle);
    EXPECT_EQ(ret, CM_SUCCESS) << "TestVerify CmInit test failed";

    ret = CmUpdate(&handle, &message);
    EXPECT_EQ(ret, CM_SUCCESS) << "TestVerify CmUpdate test failed";

    inDataFinish = { signature.size, signature.data };
    if (signature.size > 0) {
        // signature.data[0] += 0x01; /* change the first byte of signature, ignore data flipping */
    }

    struct CmBlob outDataFinish = { 0, nullptr };
    ret = CmFinish(&handle, &inDataFinish, &outDataFinish);
    EXPECT_EQ(ret, CM_SUCCESS) << "TestVerify CmFinish test failed";

    ret = CmAbort(&handle);
    EXPECT_EQ(ret, CM_SUCCESS) << "TestVerify CmAbort test failed";
}

int32_t InitCertInfo(struct CertInfo* certInfo)
{
    if (certInfo == nullptr) {
        return CMR_ERROR_MALLOC_FAIL;
    }

    certInfo->certInfo.data = static_cast<uint8_t*>(malloc(MAX_LEN_CERTIFICATE));
    if (certInfo->certInfo.data == NULL) {
        return CMR_ERROR_MALLOC_FAIL;
    }
    certInfo->certInfo.size = MAX_LEN_CERTIFICATE;

    return CM_SUCCESS;
}

int32_t InitCertList(struct CertList** cList)
{
    *cList = static_cast<struct CertList*>(malloc(sizeof(struct CertList)));
    if (*cList == nullptr) {
        return CMR_ERROR_MALLOC_FAIL;
    }

    uint32_t buffSize = MAX_COUNT_CERTIFICATE * sizeof(struct CertAbstract);
    (*cList)->certAbstract = static_cast<struct CertAbstract*>(malloc(buffSize));
    if ((*cList)->certAbstract == NULL) {
        return CMR_ERROR_MALLOC_FAIL;
    }
    (void)memset_s((*cList)->certAbstract, buffSize, 0, buffSize);
    (*cList)->certsCount = MAX_COUNT_CERTIFICATE;

    return CM_SUCCESS;
}

void FreeCertList(struct CertList* certList)
{
    if (certList == nullptr || certList->certAbstract == nullptr) {
        return;
    }

    free(certList->certAbstract);
    certList->certAbstract = nullptr;

    free(certList);
}

void FreeCMBlobData(struct CmBlob* blob)
{
    if (blob == nullptr) {
        return;
    }

    if (blob->data != nullptr) {
        free(blob->data);
        blob->data = nullptr;
    }
    blob->size = 0;
}

void FreeCertInfo(struct CertInfo* cInfo)
{
    if (cInfo == nullptr || (cInfo->certInfo).data == nullptr) {
        return;
    }

    FreeCMBlobData(&(cInfo->certInfo));
    free(cInfo);
}

void LogCertInfo(struct CertInfo* certInfo)
{
    if (certInfo) {
        printf("CertInfo:\n");
        printf("\t uri: %s\n", certInfo->uri);
        printf("\t certAlias: %s\n", certInfo->certAlias);
        printf("\t status: %d\n", certInfo->status);
        printf("\t issuerName: %s\n", certInfo->issuerName);
        printf("\t subjectName: %s\n", certInfo->subjectName);
        printf("\t serial: %s\n", certInfo->serial);
        printf("\t notBefore: %s\n", certInfo->notBefore);
        printf("\t notAfter: %s\n", certInfo->notAfter);
        printf("\t fingerprintSha256: %s\n", certInfo->fingerprintSha256);
        printf("\t certInfo.size: %d\n", certInfo->certInfo.size);
        printf("\t certInfo.data: %s\n", certInfo->certInfo.data);
    }
}

void LogCretList(struct CertList* certList)
{
    if (certList) {
        for (int i = 0; i < certList->certsCount; i++) {
            struct CertAbstract cert = certList->certAbstract[i];
            printf("Cert: %d\n", i + 1);
            printf("\t certAlias: %s\n", cert.certAlias);
            printf("\t status: %d\n", cert.status);
            printf("\t subjectName: %s\n", cert.subjectName);
        }
    }
}

void LogCredential(struct Credential* certInfo)
{
    if (certInfo) {
        printf("Certificate:\n");
        printf("\t type: %s\n", certInfo->type);
        printf("\t alias: %s\n", certInfo->alias);
        printf("\t keyUri: %s\n", certInfo->keyUri);
        printf("\t isExist: %d\n", certInfo->isExist);
        printf("\t certNum: %d\n", certInfo->certNum);
        printf("\t keyNum: %d\n", certInfo->keyNum);
        printf("\t credData.size: %d\n", certInfo->credData.size);
        printf("\t credData.data: %s\n", certInfo->credData.data);
    }
}

void LogCredentialList(struct CredentialList* certificateList)
{
    if (certificateList) {
        for (int i = 0; i < certificateList->credentialCount; i++) {
            struct CredentialAbstract cert = certificateList->credentialAbstract[i];
            printf("Certificate: %d\n", i + 1);
            printf("\t type: %s\n", cert.type);
            printf("\t alias: %s\n", cert.alias);
            printf("\t keyUri: %s\n", cert.keyUri);
        }
    }
}
